////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   decals.cpp
//  Version:     v1.00
//  Created:     28/5/2001 by Vladimir Kajalin
//  Compilers:   Visual Studio.NET
//  Description: draw, create decals on the world
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"

#include "DecalManager.h"
#include "3dEngine.h"
#include "ObjMan.h"
#include "Vegetation.h"
#include "terrain.h"
#include "VoxMan.h"

IGeometry *CDecal::s_pSphere=0;

void CDecal::ResetStaticData()
{
	SAFE_RELEASE(s_pSphere);
}

int CDecal::Update( bool & active, const float fFrameTime )
{
	// process life time and disable decal when needed
  m_fLifeTime -= fFrameTime;

  if(m_fLifeTime<0)
	{
    active=0;
		FreeRenderData();
	}	else if (m_ownerInfo.pRenderNode && m_ownerInfo.pRenderNode->m_nInternalFlags & IRenderNode::UPDATE_DECALS && !m_bDeferred)
	{
		/*Matrix34A mtx;
		IStatObj *_pStatObj = m_ownerInfo.GetOwner(mtx);
		phys_geometry *pPhysGeom;
		if (_pStatObj && _pStatObj->GetFlags() & (STATIC_OBJECT_CLONE|STATIC_OBJECT_GENERATED) && 
				(pPhysGeom=_pStatObj->GetPhysGeom(PHYS_GEOM_TYPE_DEFAULT)))
		{
			if (!s_pSphere)
			{
				primitives::sphere sph;
				sph.center.zero(); sph.r=1;
				s_pSphere = gEnv->pPhysicalWorld->GetGeomManager()->CreatePrimitive(primitives::sphere::type, &sph);
			}
			int i,j,ncont;
			geom_world_data gwd;
			intersection_params ip;
			ip.bExactBorder = 1;
			geom_contact *pcontact;
			gwd.offset = m_vPos;
			gwd.scale = m_fSize;
			{ WriteLockCond lock;
			if (ncont=pPhysGeom->pGeom->IntersectLocked(s_pSphere,0,&gwd,&ip,pcontact,lock))
			{
				Vec3 n = m_vUp^m_vRight;
				for(i=0;i<ncont;i++) 
				{
					if (pcontact[i].idxborder)
					{
						for(j=0;j<pcontact[i].nborderpt && 
								fabs_tpl(n*pPhysGeom->pGeom->GetNormal(pcontact[i].idxborder[j][0] & IDXMASK, pcontact[i].ptborder[j]))>0.85f; j++);
						if (j<pcontact[i].nborderpt) {
							active = false; break;
						}
					}
					else
					{
						active = false; break;
					}
				}
			}	else
				active = false;
			} // lock
		} else if (_pStatObj && _pStatObj->GetFlags() & STATIC_OBJECT_COMPOUND && m_ownerInfo.nRenderNodeSlotSubObjectId<0)*/
			active = false;
		/*if (!active)
		{
			Matrix34A objMat;
			IStatObj *pStatObj = m_ownerInfo.GetOwner(objMat);
			if (m_fLifeTime<10000 || !m_pRenderMesh || !pStatObj) 
				FreeRenderData();
			else {
				GetRenderer()->DeleteRenderMesh(m_pRenderMesh);
				m_pRenderMesh = m_pDecalManager->MakeBigDecalRenderMesh(pStatObj->GetRenderMesh(), m_vPos,m_fSize,m_vFront, m_pMaterial, pStatObj->GetMaterial());
				active = true;
			}
		}*/
		return 1;
	}
	return 0;
}

Vec3 CDecal::GetWorldPosition()
{
  Vec3 vPos = m_vPos;

  if(m_ownerInfo.pRenderNode)
  {
    if(m_eDecalType == eDecalType_OS_SimpleQuad || m_eDecalType == eDecalType_OS_OwnersVerticesUsed)
    {
      assert(m_ownerInfo.pRenderNode);
      if(m_ownerInfo.pRenderNode)
      {
        Matrix34A objMat;
        if(IStatObj * pEntObject = m_ownerInfo.GetOwner(objMat))
          vPos = objMat.TransformPoint(vPos);
        else if(m_ownerInfo.pRenderNode->GetRenderNodeType() == eERType_VoxelObject)
        {
          objMat = ((CVoxelObject*)m_ownerInfo.pRenderNode)->GetMatrix();
          vPos = objMat.TransformPoint(vPos);
        }
      }
    }
  }

  return vPos;
}

void CDecal::Render( const float fCurTime, int nAfterWater, uint32 nDLMask, float fDistanceFading, float fDistance)
{
  FUNCTION_PROFILER_3DENGINE;

  // Get decal alpha from life time
  float fAlpha = m_fLifeTime*2;

  if(fAlpha > 1.f)
    fAlpha = 1.f;
  else if(fAlpha<0)
    return;

  fAlpha *= fDistanceFading;

  float fSizeK;
  if(m_fGrowTime)
    fSizeK = min(1.f, cry_sqrtf((fCurTime - m_fLifeBeginTime)/m_fGrowTime));
  else
    fSizeK = 1.f;

  float fSizeAlphaK;
  if(m_fGrowTimeAlpha)
    fSizeAlphaK = min(1.f, cry_sqrtf((fCurTime - m_fLifeBeginTime)/m_fGrowTimeAlpha));
  else
    fSizeAlphaK = 1.f;

  if(m_bDeferred)
  {
    SDeferrredDecal newItem;
    newItem.fAlpha = fAlpha;
    newItem.pMaterial = m_pMaterial;
    newItem.nSortOrder = m_sortPrio;
    newItem.nFlags = 0;

		Vec3 vRight = m_vRight*m_fSize;
		Vec3 vUp    = m_vUp*m_fSize;
		Vec3 vNorm  = m_vRight.Cross(m_vUp)*m_fSize;

    Matrix34A objMat;
    if(IStatObj * pEntObject = m_ownerInfo.GetOwner(objMat))
    {
      vRight = objMat.TransformVector(vRight);
      vUp    = objMat.TransformVector(vUp);
      vNorm  = objMat.TransformVector(vNorm);
    }

    Matrix33 matRotation;
    matRotation.SetColumn(0, vRight);
    matRotation.SetColumn(1, vUp);
    matRotation.SetColumn(2, vNorm*GetFloatCVar(e_DecalsDefferedDynamicDepthScale) );
    newItem.projMatrix.SetRotation33(matRotation);
    newItem.projMatrix.SetTranslation(m_vWSPos);    

		if(m_fGrowTimeAlpha)
			newItem.fGrowAlphaRef = max(.02f, 1.f - fSizeAlphaK);
		else
			newItem.fGrowAlphaRef = 0;

    GetRenderer()->EF_AddDeferrredDecal(newItem);
    return;
  }

	switch(m_eDecalType)
	{
		case eDecalType_WS_Merged:
		case eDecalType_OS_OwnersVerticesUsed:
		{
      // check if owner mesh was deleted
      if(m_pRenderMesh && (m_pRenderMesh->GetVertexContainer()==m_pRenderMesh) && m_pRenderMesh->GetVerticesCount()<3)
        FreeRenderData();

			if(!m_pRenderMesh)
				break;

			// setup transformation
			CRenderObject * pObj = GetRenderer()->EF_GetObject_Temp();
      if (!pObj)
        return;
			pObj->m_fSort = 0;
			pObj->m_RState = 0;

			Matrix34A objMat;
			if(m_ownerInfo.pRenderNode && !m_ownerInfo.GetOwner(objMat))
			{ 
        if(m_ownerInfo.pRenderNode->GetRenderNodeType() == eERType_VoxelObject)
        {
          CVoxelObject * pVox = (CVoxelObject*)m_ownerInfo.pRenderNode;
          objMat = pVox->GetMatrix();
        }
        else
        {
          assert(0); 
          return; 
        }
      }
			else
      if(!m_ownerInfo.pRenderNode)
      {
        objMat.SetIdentity();
        if(m_eDecalType == eDecalType_WS_Merged)
        {
          objMat.SetTranslation(m_vPos);
          pObj->m_ObjFlags |= FOB_TRANS_MASK;
        }
      }

			pObj->m_II.m_Matrix = objMat;
			if(m_ownerInfo.pRenderNode)
				pObj->m_ObjFlags |= FOB_TRANS_MASK;

			pObj->m_DynLMMask[m_nRenderThreadListID] = nDLMask;
			pObj->m_nSort = m_sortPrio;
			
			// somehow it's need's to be twice bigger to be same as simple decals
			float fSize2 = m_fSize*fSizeK*2.f;///m_ownerInfo.pRenderNode->GetScale();
			if(fSize2<0.0001f)
				return;

			// setup texgen
			// S component
			float correctScale( -1 ); 
			m_arrBigDecalRMCustomData[0] = correctScale * m_vUp.x/fSize2;
			m_arrBigDecalRMCustomData[1] = correctScale * m_vUp.y/fSize2;
			m_arrBigDecalRMCustomData[2] = correctScale * m_vUp.z/fSize2;

      Vec3 vPosDecS = m_vPos;
      if(m_eDecalType == eDecalType_WS_Merged)
        vPosDecS.zero();
																				
			float D0 = 
				m_arrBigDecalRMCustomData[0]*vPosDecS.x + 
				m_arrBigDecalRMCustomData[1]*vPosDecS.y + 
				m_arrBigDecalRMCustomData[2]*vPosDecS.z;

			m_arrBigDecalRMCustomData[3] = -D0+0.5f;

			// T component
			m_arrBigDecalRMCustomData[4] = m_vRight.x/fSize2;
			m_arrBigDecalRMCustomData[5] = m_vRight.y/fSize2;
			m_arrBigDecalRMCustomData[6] = m_vRight.z/fSize2;

			float D1 = 
				m_arrBigDecalRMCustomData[4]*vPosDecS.x + 
				m_arrBigDecalRMCustomData[5]*vPosDecS.y + 
				m_arrBigDecalRMCustomData[6]*vPosDecS.z;

			m_arrBigDecalRMCustomData[7] = -D1+0.5f;

			// pass attenuation info
			m_arrBigDecalRMCustomData[8] = vPosDecS.x;
			m_arrBigDecalRMCustomData[9] = vPosDecS.y;
			m_arrBigDecalRMCustomData[10]= vPosDecS.z;
			m_arrBigDecalRMCustomData[11]= m_fSize;

			// N component
			Vec3 vNormal( Vec3( correctScale * m_vUp ).Cross( m_vRight ).GetNormalized() );
			m_arrBigDecalRMCustomData[12] = vNormal.x*(m_fSize/m_fWSSize);
			m_arrBigDecalRMCustomData[13] = vNormal.y*(m_fSize/m_fWSSize);
			m_arrBigDecalRMCustomData[14] = vNormal.z*(m_fSize/m_fWSSize);
			m_arrBigDecalRMCustomData[15] = 0;

      CStatObj * pBody = NULL;
      bool bUseBending = GetCVars()->e_VegetationBending != 0;
			if(m_ownerInfo.pRenderNode && m_ownerInfo.pRenderNode->GetRenderNodeType() == eERType_Vegetation)
			{
        pObj->m_ObjFlags |= FOB_VEGETATION;
				CObjManager * pObjManager = GetObjManager();
				CVegetation * pVegetation = (CVegetation *)m_ownerInfo.pRenderNode;
				pBody = pVegetation->GetStatObj();
				assert(pObjManager && pVegetation && pBody);

				if(pVegetation && pBody && bUseBending)
				{
				  pBody->SetupBending(pObj, pVegetation);
				}
        IMaterial *pMat = m_ownerInfo.pRenderNode->GetMaterial();
        pMat = pMat->GetSubMtl(m_ownerInfo.nMatID);
        if (pMat)
        {
          // Support for public parameters from owner (deformations)
          SShaderItem& SH = pMat->GetShaderItem();
          if (SH.m_pShaderResources)
          {
            IMaterial *pMatDecal = m_pMaterial;
            SShaderItem& SHDecal = pMatDecal->GetShaderItem();
            if (bUseBending)
            {
              pObj->m_nMDV = SH.m_pShader->GetVertexModificator();
              if (SHDecal.m_pShaderResources && pObj->m_nMDV)
                SH.m_pShaderResources->ExportModificators(SHDecal.m_pShaderResources, pObj);
            }
          }
        }
        if (m_eDecalType == eDecalType_OS_OwnersVerticesUsed)
          pObj->m_ObjFlags |= FOB_OWNER_GEOMETRY;
        IFoliage *pFol = pVegetation->GetFoliage();
        if (pFol)
        {
          SRenderObjData *pOD = GetRenderer()->EF_GetObjData(pObj, true);
          if (pOD) 
          {
            pOD->m_pCharInstance = pFol;
          }
        }
        if (pBody && pBody->m_pRenderMesh)
        {
          PodArray<CRenderChunk>& Chunks = m_pRenderMesh->GetChunks();
          if (Chunks.size())
            Chunks[0].m_arrChunkBoneIDs.Clear();
          m_pRenderMesh->SetVertexContainer(pBody->m_pRenderMesh);
          if (pFol)
          {
            // Support for skinning
            PodArray<CRenderChunk> *pCS = pBody->m_pRenderMesh->GetChunksSkinned();
            if (pCS)
            {
              for (uint32 i=0; i<pCS->size(); i++)
              {
                CRenderChunk *pCH = &(*pCS)[i];
                if (pCH->m_nMatID == m_ownerInfo.nMatID)
                {
                  Chunks[0].m_arrChunkBoneIDs.AddList(pCH->m_arrChunkBoneIDs.begin(), pCH->m_arrChunkBoneIDs.size());
                  break;
                }
              }
            }
          }
        }
			}

			// draw complex decal using new indices and original object vertices
			pObj->m_fAlpha = fAlpha;
			pObj->m_ObjFlags |= FOB_DECAL | FOB_DECAL_TEXGEN_2D | FOB_NO_Z_PASS | FOB_INSHADOW;
			pObj->m_nTextureID = -1;	
			//pObj->m_nTextureID1 = -1;	
			pObj->m_II.m_AmbColor = m_vAmbient;
			if (GetCVars()->e_DecalsScissor)
			{
				Matrix44 view;
				GetRenderer()->GetModelViewMatrix(view.GetData());

				Matrix44 proj;
				GetRenderer()->GetProjectionMatrix(proj.GetData());

				Vec4 viewspacePos = Vec4(m_vWSPos, 1) * view;
				//if (fabsf(viewspacePos.z) >= 1e-4f && fabsf(proj.m23) == 1)
				if (viewspacePos.z < -1e-4f)
				{
					int width = GetRenderer()->GetWidth();
					int height = GetRenderer()->GetHeight();

					float corner1x = viewspacePos.x - 1.5f * m_fWSSize;
					float corner1y = viewspacePos.y + 1.5f * m_fWSSize;
					float corner2x = viewspacePos.x + 1.5f * m_fWSSize;
					float corner2y = viewspacePos.y - 1.5f * m_fWSSize;

					float w = 0.5f / viewspacePos.z * proj.m23;
					float scissorMinX =  (corner1x * proj.m00) * w;
					float scissorMinY = -(corner1y * proj.m11) * w;
					float scissorMaxX =  (corner2x * proj.m00) * w;
					float scissorMaxY = -(corner2y * proj.m11) * w;

					uint16 scissorX1 = max(min((int)(width  * (scissorMinX + 0.5f)), width ), 0);
					uint16 scissorY1 = max(min((int)(height * (scissorMinY + 0.5f)), height), 0);
					uint16 scissorX2 = max(min((int)(width  * (scissorMaxX + 0.5f)), width ), 0);
					uint16 scissorY2 = max(min((int)(height * (scissorMaxY + 0.5f)), height), 0);

					if (scissorX1 < scissorX2 && scissorY1 < scissorY2)
					{
						SRenderObjData* pOD = GetRenderer()->EF_GetObjData(pObj, true);
						pOD->m_scissorX = scissorX1;
						pOD->m_scissorY = scissorY1;
						pOD->m_scissorWidth = scissorX2 - scissorX1;
						pOD->m_scissorHeight = scissorY2 - scissorY1;
					}
				}
			}
			m_pRenderMesh->SetREUserData(m_arrBigDecalRMCustomData, 0, fAlpha);
      m_pRenderMesh->AddRenderElements(m_pMaterial, pObj, EFSLIST_GENERAL, nAfterWater);
		}
		break;

		case eDecalType_OS_SimpleQuad:
		{ 
			assert(m_ownerInfo.pRenderNode);
			if(!m_ownerInfo.pRenderNode)
				break;

			// transform decal in software from owner space into world space and render as quad
			Matrix34A objMat;
			IStatObj * pEntObject = m_ownerInfo.GetOwner(objMat);
			if(!pEntObject)
				break;

			Vec3 vPos	 = objMat.TransformPoint(m_vPos);
			Vec3 vRight = objMat.TransformVector(m_vRight*m_fSize);
			Vec3 vUp    = objMat.TransformVector(m_vUp*m_fSize);
			UCol uCol; 
			
			uCol.dcolor = 0xffffffff; 
			uCol.bcolor[3] = fastround_positive(fAlpha*255);

			GetObjManager()->AddDecalToRenderer( fDistance, m_pMaterial, nDLMask, m_sortPrio, vRight * fSizeK, vUp * fSizeK, uCol, 
				OS_ALPHA_BLEND, m_vAmbient, vPos, nAfterWater,
				m_ownerInfo.pRenderNode->GetRenderNodeType() == eERType_Vegetation ? (CVegetation*) m_ownerInfo.pRenderNode : 0 );
		}
		break;

		case eDecalType_WS_SimpleQuad:
		{	// draw small world space decal untransformed
			UCol uCol; 
			uCol.dcolor = 0;
			uCol.bcolor[3] = fastround_positive(fAlpha*255);

      GetObjManager()->AddDecalToRenderer( fDistance, m_pMaterial, nDLMask, m_sortPrio, m_vRight * m_fSize * fSizeK, 
				m_vUp * m_fSize * fSizeK, uCol, OS_ALPHA_BLEND, m_vAmbient, m_vPos, nAfterWater );
		}
		break;

		case eDecalType_WS_OnTheGround:
		{
			RenderBigDecalOnTerrain(fAlpha, fSizeK, nDLMask);
		}
		break;
	}
}

void CDecal::FreeRenderData()
{
	// delete render mesh
	GetRenderer()->DeleteRenderMesh(m_pRenderMesh);
	m_pRenderMesh = NULL;

	m_ownerInfo.pRenderNode=0;
}
/*
void CDecal::Render3DObject()
{
  // draw
  if(m_pStatObj)
  {
  	Ang3 vAngles = Ang3(m_vFront);
    vAngles.x+=90;
		Matrix34 mat = Matrix34::CreateRotationXYZ( DEG2RAD(vAngles), m_vPos );

		Matrix34 objMat;
		if(IStatObj * pEntObject = m_ownerInfo.GetOwner(objMat))
			mat = objMat*mat;

    SRendParams rParms;
    rParms.pMatrix = &mat;
    rParms.dwFObjFlags |= FOB_TRANS_MASK;
    m_pStatObj->Render(rParms);
  }
}
*/
void CDecal::RenderBigDecalOnTerrain(float fAlpha, float fScale, uint32 nDLMask)
{
	float fRadius = m_fSize * fScale;

	// check terrain bounds
	if(m_vPos.x < -fRadius || m_vPos.y < -fRadius)
		return;
	if(m_vPos.x >= CTerrain::GetTerrainSize() + fRadius || m_vPos.y >= CTerrain::GetTerrainSize() + fRadius)
		return;

	const int nUsintSize = CTerrain::GetHeightMapUnitSize();
	fRadius += nUsintSize;

	if( fabs(m_vPos.z - Get3DEngine()->GetTerrainZ(int(m_vPos.x),int(m_vPos.y))) > fRadius )
		return; // too far from ground surface

	// setup texgen
	float fSize2 = m_fSize*fScale*2.f;
	if(fSize2<0.05f)
		return;

	// S component
	float correctScale( -1 );
	m_arrBigDecalRMCustomData[0] = correctScale * m_vUp.x/fSize2;
	m_arrBigDecalRMCustomData[1] = correctScale * m_vUp.y/fSize2;
	m_arrBigDecalRMCustomData[2] = correctScale * m_vUp.z/fSize2;

	float D0 = 0;

	m_arrBigDecalRMCustomData[3] = -D0+0.5f;

	// T component
	m_arrBigDecalRMCustomData[4] = m_vRight.x/fSize2;
	m_arrBigDecalRMCustomData[5] = m_vRight.y/fSize2;
	m_arrBigDecalRMCustomData[6] = m_vRight.z/fSize2;

	float D1 = 0;

	m_arrBigDecalRMCustomData[7] = -D1+0.5f;

	// pass attenuation info
	m_arrBigDecalRMCustomData[8] = 0;
	m_arrBigDecalRMCustomData[9] = 0;
	m_arrBigDecalRMCustomData[10]= 0;
	m_arrBigDecalRMCustomData[11]= fSize2;

	Vec3 vNormal( Vec3( correctScale * m_vUp ).Cross( m_vRight ).GetNormalized() );
	m_arrBigDecalRMCustomData[12] = vNormal.x;
	m_arrBigDecalRMCustomData[13] = vNormal.y;
	m_arrBigDecalRMCustomData[14] = vNormal.z;
	m_arrBigDecalRMCustomData[15] = 0;

	CRenderObject * pObj = GetIdentityCRenderObject();
  if (!pObj)
    return;

  pObj->m_II.m_Matrix.SetTranslation(m_vPos);
  pObj->m_ObjFlags |= FOB_TRANS_TRANSLATE;

	pObj->m_fAlpha = fAlpha;
	pObj->m_ObjFlags |= FOB_DECAL | FOB_DECAL_TEXGEN_2D | FOB_NO_Z_PASS | FOB_INSHADOW;
	pObj->m_nTextureID = -1;
	//pObj->m_nTextureID1 = -1;
	pObj->m_II.m_AmbColor = m_vAmbient;

	pObj->m_nSort = m_sortPrio;

  pObj->m_DynLMMask[m_nRenderThreadListID] = nDLMask;

  Plane planes[4];
  planes[0].SetPlane(  m_vRight, m_vRight*m_fSize + m_vPos);
  planes[1].SetPlane( -m_vRight,-m_vRight*m_fSize + m_vPos);
  planes[2].SetPlane(  m_vUp,    m_vUp*m_fSize    + m_vPos);
  planes[3].SetPlane( -m_vUp,   -m_vUp*m_fSize    + m_vPos);

	// m_pRenderMesh might get updated by the following function
	GetTerrain()->RenderArea( m_vPos, fRadius, &m_pRenderMesh,
    pObj, m_pMaterial, "BigDecalOnTerrain", m_arrBigDecalRMCustomData, GetCVars()->e_DecalsClip ? planes : NULL);
}
